%{
    #include <stdint.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include "fonctions.h"

    extern FILE *yyin;
    extern FILE *yyout;

    int yylex(void);
    void yyerror(char *);

    extern int nline;
    int g_type;
%}

%union {
    int nombre;
    float reel;
    char *chaine;
    union node *node;
    struct expression *expr;
    struct ident *identifiant;
}

%token <chaine> IDENT STRING
%token INT_T FLOAT_T BOOL_T MAIN
%token <nombre> TRUE FALSE
%token IF ELSE FOR WHILE
%token GE LE EQ NE AND OR
%token BOOL
%token <nombre> INT
%token <reel> FLOAT
%token POINTEUR POINTEUR_INT POINTEUR_FLOAT
%token DEF EXP READ WRITE
%token NEG
%token GOTO
%token EXIT

%right '='
%left PLUS_PLUS PLUS_EGAL MOINS_MOINS MOINS_EGAL
%left OR
%left AND
%left '!'
%left '%'
%left '+' '-'
%left '*' '/'
%left UNARY
/* utiliser pour separer le if et le if else unaire avec une seule instruction */
%nonassoc VIDE
%nonassoc ELSE

%type <nombre> type oprel pointeur constbool increment
%type <identifiant> id
%type <expr> expression exp expression_log expl m n instruction list_instructions affectation io_instruction base_instruction list_affectation ternaire

%start prog
%%
prog : main bloc                                { end();   }
main : MAIN '(' ')'                             { start(); }
bloc : '{' declarations list_instructions '}'

declarations : type id_list ';' declarations
             |

/* Suite d'identifiants, séparés par une virgule */
id_list : id_list ',' id                        {
            s_table *table_symboles = ($3->pointeur > 0) ? table_pointeur : ((g_type == INT) ? table_int : table_float);

            if(find_cell(table_int, $3->name) == NULL && find_cell(table_float, $3->name) == NULL
                && find_cell(table_pointeur, $3->name) == NULL) {
                s_cell *tmp_cell = add_cell(table_symboles, $3->name, g_type);
                tmp_cell->pointeur = $3->pointeur;
            }
            else {
                yyerror("variable already declared");
            }
        }
        | id                                    {
            s_table *table_symboles = ($1->pointeur > 0) ? table_pointeur : ((g_type == INT) ? table_int : table_float);

            if(find_cell(table_int, $1->name) == NULL && find_cell(table_float, $1->name) == NULL
                && find_cell(table_pointeur, $1->name) == NULL) {
                s_cell *tmp_cell = add_cell(table_symboles, $1->name, g_type);
                tmp_cell->pointeur = $1->pointeur;
            }
            else {
                yyerror("variable already declared");
            }
        }

list_instructions : instruction list_instructions {
                        #ifdef DEBUG
                        printf("on passe dans le instruction liste_instructions\n");
                        #endif
                        $$ = malloc ( sizeof( s_expression ));
                        if ( $2->next != NULL )
                            $$->next = concat( $1->next , $2->next );
                        else
                            $$->next = $1->next;

                        free( $1 );
                  }
                  |   {
                        #ifdef DEBUG
                        printf("on passe dans le liste_instruction instructions qui est vide omg\n");
                        #endif
                    $$ = malloc ( sizeof( s_expression ));
                  }

/* nouvelle affectation pour l'utiliser dans le for sans ';' */
list_affectation : affectation ',' list_affectation {
                    $$ = malloc ( sizeof( s_expression ));
                    $$->next = NULL;
                 }
                 | affectation {
                    $$ = malloc ( sizeof( s_expression ));
                    $$->next = NULL;
                 }

affectation : id '=' expression        {
                #ifdef DEBUG
                printf("on passe dans l'affectation\n");
                #endif

                s_expression *tmp_expr = id($1);

                /* Pointeur */
                if(tmp_expr->pointeur > 0) {
                    if($3->type != tmp_expr->type &&
                        (($3->type == POINTEUR_INT && tmp_expr->type != INT) ||
                        ($3->type == POINTEUR_FLOAT && tmp_expr->type != FLOAT))) {
                        yyerror("incompatible types");
                    }
                    else if($1->pointeur < tmp_expr->pointeur && $3->pointeur == 0) {
                        yyerror("assignment makes pointer from integer");
                    }
                    else if($3->pointeur != tmp_expr->pointeur &&
                            $1->pointeur != tmp_expr->pointeur) {
                        yyerror("assignment from incompatible pointer type");
                    }
                    else {
                        gencode_pointer('=', $3->ptr, NULL, tmp_expr->ptr, ($3->type == FLOAT) ? INT : $3->type, 0, 0, $1->pointeur);
                    }
                }
                else if($3->type != tmp_expr->type) {
                    yyerror("incompatibles types");
                }
                /* Affectation */
                else {
                    gencode_pointer('=', $3->ptr, NULL, tmp_expr->ptr, tmp_expr->type, $3->pointeur, 0, $1->pointeur);
                }

                $$ = malloc ( sizeof( s_expression ));
                $$->next = NULL;
            }
            | id increment  {

                s_expression *tmp_expr = id($1);
                int type = tmp_expr->type;

                if( type != INT && type != FLOAT )
                    yyerror("operation only allowed on INT and FLOAT");

                // initialisation de la variable utilisée pour les ++ et les --
                if ( $2 == 1 )
                {
                    if ( type == INT && increment_int == NULL )
                        increment_int = newint(1);
                    else if ( type == FLOAT && increment_float == NULL )
                        increment_float = newfloat(1);

                    gencode('+', tmp_expr->ptr, ( type == INT ) ? increment_int->ptr : increment_float->ptr , tmp_expr->ptr, type);
                }
                else if ( $2 == -1 )
                {
                    if ( type == INT && decrement_int == NULL )
                        decrement_int = newint(-1);
                    else if ( type == FLOAT && decrement_float == NULL )
                        decrement_float = newfloat(-1);

                    gencode('+', tmp_expr->ptr, ( type == INT ) ? decrement_int->ptr : decrement_float->ptr , tmp_expr->ptr, type);
                }
                else
                    yyerror("unknown affectation operation");

                $$ = malloc ( sizeof( s_expression ));
                $$->next = NULL;
            }

increment : PLUS_PLUS            { $$ = 1; }
          | MOINS_MOINS          { $$ = -1;}
/*
          | PLUS_EGAL inc_value  {
            $$ = $2;
          }
          | MOINS_EGAL inc_value {
            $$ = $2;
          }

inc_value : id
          | INT
          | FLOAT
*/

io_instruction : EXIT  {
                #ifdef DEBUG
                printf("on passe dans le EXIT\n");
                #endif
                gencode( EXIT , NULL , NULL , NULL , 0 );

                $$ = malloc ( sizeof( s_expression ));
             }
             | READ id                 {
                #ifdef DEBUG
                printf("on passe dans le READ\n");
                #endif
                s_expression *tmp_expr = id($2);
                gencode_pointer(READ, tmp_expr->ptr, NULL, NULL, tmp_expr->type, $2->pointeur, 0, 0);

                $$ = malloc ( sizeof( s_expression ));
            }  /* Instruction de lecture */
            | WRITE id                   {
                #ifdef DEBUG
                printf("on passe dans le WRITE id\n");
                #endif
                s_expression *tmp_expr = id($2);
                gencode_pointer(WRITE, tmp_expr->ptr, NULL, NULL, tmp_expr->type, $2->pointeur, 0, 0);

                $$ = malloc ( sizeof( s_expression ));
            } /* Instruction d'écriture */
            | WRITE INT                  {
                #ifdef DEBUG
                printf("on passe dans le WRITE INT\n");
                #endif
                s_expression *tmp_expr = newint($2);
                gencode(WRITE, tmp_expr->ptr, NULL, NULL, tmp_expr->type);

                $$ = malloc ( sizeof( s_expression ));
            } /* Instruction d'écriture */

            | WRITE FLOAT                  {
                #ifdef DEBUG
                printf("on passe dans le WRITE FLOAT\n");
                #endif
                s_expression *tmp_expr = newfloat($2);
                gencode(WRITE, tmp_expr->ptr, NULL, NULL, tmp_expr->type);

                $$ = malloc ( sizeof( s_expression ));
            }
            | WRITE STRING                   {
                #ifdef DEBUG
                printf("on passe dans le WRITE string\n");
                #endif
                s_expression *tmp_expr = newstring($2);
                gencode(WRITE, tmp_expr->ptr, NULL, NULL, tmp_expr->type);

                $$ = malloc ( sizeof( s_expression ));
            }

base_instruction : affectation          {
                        $$ = $1;
                 }

                 | io_instruction       {
                        $$ = $1;
                 }

                 | ternaire             {
                        $$ = $1;
                 }


            // pour que ca marche a fond faire renvoyer des valeurs aux instructions
        //     1         2        3   4   5     6             7  8     9
ternaire    : '(' expression_log ')' '?'  m base_instruction ':' n base_instruction  {

                #ifdef DEBUG
                printf("je passe dans le ternaire classique\n");
                #endif

                $$ = malloc ( sizeof( s_expression ));
                complete( $2->true, $5->quad );
                complete( $2->false , $8->quad );
                $$->next = concat( $2->false , concat ( $8->next , $9->next ));
                complete( $$->next , nextquad() );
            }
            /* peut être pas la bonne solution, a voir
            | id '=' '(' expression_log ')' '?'  m expression ':' n expression  {
                if ( DEBUG )
                    printf("je passe dans le ternaire affectation\n");

                $$ = malloc ( sizeof( s_expression ));
                complete( $4->true, $7->quad );
                complete( $4->false , $10->quad );
                $$->next = concat( $4->false , concat ( $10->next , $11->next ));
                complete( $$->next , nextquad() );
            }
            | WRITE '(' expression_log ')' '?'  m INT ':' n INT  {
                if ( DEBUG )
                    printf("je passe dans le ternaire affichage\n");

                $$ = malloc ( sizeof( s_expression ));
                complete( $3->true, $6->quad );
                complete( $3->false , $9->quad );
                $$->next = concat( $3->false , concat ( $9->next , $10->next ));
                complete( $$->next , nextquad() );

                s_expression *tmp_expr = newint($2);
                gencode(WRITE, tmp_expr->ptr, NULL, NULL, tmp_expr->type);
            }
            */

instruction : base_instruction ';' {
                $$ = $1;
            }

            | '{' list_instructions '}' {
                #ifdef DEBUG
                printf("on passe dans instruction : { list_instructions }\n");
                #endif
                $$ = $2;
            }
        //    1   2        3         4  5     6
            | IF '(' expression_log ')' m instruction  %prec VIDE {
                #ifdef DEBUG
                printf("je passe dans le IF\n");
                #endif

                $$ = malloc ( sizeof( s_expression ));
                complete( $3->true, $5->quad );
                $$->next = concat( $3->false , $6->next );
                complete( $$->next , nextquad() );
                free( $5 );
            }
        //    1   2        3         4  5     6        7   8     9
            | IF '(' expression_log ')' m instruction ELSE n instruction  {
                #ifdef DEBUG
                printf("je passe dans le IF ELSE\n");
                #endif

                $$ = malloc ( sizeof( s_expression ));
                complete( $3->true, $5->quad );
                complete( $3->false , $8->quad );
                $$->next = concat( $3->false , concat ( $8->next , $9->next ));
                complete( $$->next , nextquad() );
                free( $5 );
            }

           //   1    2  3         4         5   6        7
            | WHILE '(' m   expression_log ')'  m   instruction   {
                #ifdef DEBUG
                printf("je passe dans le WHILE\n");
                #endif

                $$ = malloc ( sizeof( s_expression ));
                complete ( $4->true , $6->quad );
                    char *label = malloc(20);
                    sprintf( label , "l%d" , $3->quad );
                gencode( GOTO , NULL , NULL, label , 0 );
                complete( $7->next , $3->quad );
                $$->next = $4->false;
                complete( $$->next , nextquad() );
            }
//             1   2         3          4   5       6         7  8      9       10  11  12    13
            | FOR '(' list_affectation ';'  m expression_log ';' m affectation  n  ')'  m instruction  {
            // le casse tete chinois :)
                #ifdef DEBUG
                printf("je passe dans le FOR \n");
                #endif

                $$ = malloc ( sizeof( s_expression ));
                complete ( $6->true , $12->quad );
                    char *label = malloc(20);
                    sprintf( label , "l%d" , $8->quad );
                gencode( GOTO , NULL , NULL, label , 0 );
                complete( $13->next , $8->quad );
                complete( $10->next , $5->quad );
                $$->next = $6->false;
                complete( $$->next , nextquad() );
            }

m    :                                          {
        // M = nextquad
        #ifdef DEBUG
        printf("on passe dans m\n");
        #endif
        $$ = malloc( sizeof (s_expression));
        $$->quad = nextquad();
     }

n    :                                          {
        #ifdef DEBUG
        printf("on passe dans n\n");
        #endif
        s_quad *tmp_nextquad;
        $$ = malloc( sizeof (s_expression));
        tmp_nextquad = gencode( GOTO , NULL , NULL , NULL , 0 );
        $$->next = creeListe( tmp_nextquad );
        $$->quad = nextquad();
     }


type : INT_T                                    { g_type = INT; }
     | FLOAT_T                                  { g_type = FLOAT; }
     | BOOL_T                                   { g_type = BOOL; }

id : pointeur IDENT                             {
            $$ = malloc(sizeof(s_ident));
            $$->name = strdup($2);
            $$->pointeur = $1;
        }

/* Une ou plusieurs etoiles */
pointeur : '*' pointeur { $$ = 1 + $2; }
         |              { $$ = 0; }

expression : expression '+' expression                  {
                if($1->type == POINTEUR_INT || $3->type == POINTEUR_FLOAT) {
                    yyerror("invalid operands");
                }
                else if($1->type != $3->type) {
                    yyerror("incompatibles types");
                }
                else {
                    $$ = newtmp($1->type);
                    gencode_pointer('+', $1->ptr, $3->ptr, $$->ptr, $1->type, $1->pointeur, $3->pointeur, 0);
                }
                free($1);
                free($3);
           }
           | expression '-' expression                  {
                if($1->type == POINTEUR_INT || $3->type == POINTEUR_FLOAT) {
                    yyerror("invalid operands");
                }
                else if($1->type != $3->type) {
                    yyerror("incompatibles types");
                }
                else {
                    $$ = newtmp($1->type);
                    gencode_pointer('-', $1->ptr, $3->ptr, $$->ptr, $1->type, $1->pointeur, $3->pointeur, 0);
                }
                free($1);
                free($3);
           }
           | expression '*' expression                  {
                if($1->type == POINTEUR_INT || $3->type == POINTEUR_FLOAT) {
                    yyerror("invalid operands");
                }
                else if($1->type != $3->type) {
                    yyerror("incompatibles types");
                }
                else {
                    $$ = newtmp($1->type);
                    gencode_pointer('*', $1->ptr, $3->ptr, $$->ptr, $1->type, $1->pointeur, $3->pointeur, 0);
                }
                free($1);
                free($3);
           }
           | expression '/' expression                  {
                if($1->type == POINTEUR_INT || $3->type == POINTEUR_FLOAT) {
                    yyerror("invalid operands");
                }
                else if($1->type != $3->type) {
                    yyerror("incompatibles types");
                }
                else {
                    $$ = newtmp($1->type);
                    gencode_pointer('/', $1->ptr, $3->ptr, $$->ptr, $1->type, $1->pointeur, $3->pointeur, 0);
                }
                free($1);
                free($3);
           }
           | expression '%' expression                  {
                if($1->type == POINTEUR_INT || $3->type == POINTEUR_FLOAT ||
                    $1->type == FLOAT || $3->type == FLOAT) {
                    yyerror("invalid operands");
                }
                else if($1->type != $3->type) {
                    yyerror("incompatibles types");
                }
                else {
                    $$ = newtmp($1->type);
                    gencode_pointer('%', $1->ptr, $3->ptr, $$->ptr, $1->type, $1->pointeur, $3->pointeur, 0);
                }
                free($1);
                free($3);
           }
           | exp                                { $$ = $1; }

exp : id                                        { $$ = id($1); $$->pointeur = $1->pointeur;}
    | '&' IDENT                          {
        s_ident *tmp_id = malloc(sizeof(s_ident));
        tmp_id->name = strdup($2);

        $$ = id(tmp_id);
        $$->type = ($$->type == INT) ? POINTEUR_INT : POINTEUR_FLOAT;
        $$->pointeur++;
        free(tmp_id);
    }
    | '(' expression ')'                        { $$ = $2 }
    | '-' expression                                   %prec UNARY
    {
        if($2->pointeur > 0) {
            yyerror("wrong type argument to unary minus");
        }
        else if($2->type == INT) {
            $$ = newtmpint();
        }
        else {
            $$ = newtmpfloat();
        }
        gencode(NEG, $2->ptr, NULL, $$->ptr, $2->type);
        free($2);
    }
    | INT                                       { $$ = newint($1); }
    | FLOAT                                     { $$ = newfloat($1); }

expression_log : expression_log AND m expression_log {
                    #ifdef DEBUG
                    printf("on passe dans expression_log AND expression_log\n");
                    #endif
                    $$ = malloc ( sizeof( s_expression ));

                    complete( $1->true , $3->quad );
                    $$->true = $4->true;
                    $$->false = concat( $1->false , $4->false );
                    free( $1 );
                    free( $4 );
               }
               | expression_log OR m expression_log  {
                    #ifdef DEBUG
                    printf("on passe dans expression_log OR expression_log\n");
                    #endif
                    $$ = malloc ( sizeof( s_expression ));

                    complete( $1->false , $3->quad );
                    $$->true = concat( $1->true , $4->true );
                    $$->false = $4->false;
                    free( $1 );
                    free( $4 );
               }
               | '!' expression_log                  {
                    #ifdef DEBUG
                    printf("on passe dans ! expression_log\n");
                    #endif
                    $$ = malloc ( sizeof( s_expression ));

                    $$->true = $2->false;
                    $$->false = $2->true;
                    free($2);
               }
               | expl                           {
                    #ifdef DEBUG
                    printf("on passe dans expression_log : expl\n");
                    #endif
                    $$ = $1;
               }


expl : '(' expression_log ')'                   {
        #ifdef DEBUG
        printf("on passe dans expl : ( expression_log )\n");
        #endif
        $$ = $2;
     }
     | constbool                                {
        #ifdef DEBUG
        printf("on passe dans expl : constbool\n");
        #endif

        // initialisation de la variable utilisée pour les valeurs booléennes
        if ( assert == NULL )
            assert = newint(0);

        $$ = newint( $1 );
        s_quad *nextquad;
        nextquad = gencode( NE, $$->ptr , assert->ptr , NULL , 0 );
        $$->true = creeListe( nextquad );
        // gencode ( goto _ )
        nextquad = gencode( GOTO , NULL , NULL , NULL , 0 );
        $$->false = creeListe( nextquad );
     }
     | exp oprel exp                            {
        #ifdef DEBUG
        printf("on passe dans exp oprel exp\n");
        #endif
        s_quad *nextquad;
        // gencode ( if exp1.ptr oprel exp2.ptr goto _ )
        nextquad = gencode( $2 , $1->ptr , $3->ptr , NULL , 0 );
        $$->true = creeListe( nextquad );
        // gencode ( goto _ )
        nextquad = gencode( GOTO , NULL , NULL , NULL , 0 );
        $$->false = creeListe( nextquad );
     }

constbool : TRUE                                { $$ = 1; }
          | FALSE                               { $$ = 0; }
/*
        test pour mettre un int en condition à la place de true ou false
        | INT                                 { $$ = $1; }
        | exp // ca serais interessant que les exp renvoient une valeur bool aussi
*/
oprel : '>'                                     { $$ = '>';}
      | GE                                      { $$ = GE; }
      | '<'                                     { $$ = '<';}
      | LE                                      { $$ = LE; }
      | EQ                                      { $$ = EQ; }
      | NE                                      { $$ = NE; }

%%

void yyerror(char *s) {
    fprintf(stderr, "%s, line %d : %s\n", in, nline , s);
    remove( out );
    exit(1);
}

int main(int argc, char **argv) {
    /* Vérification des arguments */
    if(argc == 2) {
        if(!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            printf("Usage: %s FILE\n", argv[0]);
            return 0;
        }
        /* Si un nom de fichier est passé en argument, on le met en tant qu'entrée */
        else {
            strcpy ( in , argv[1]);
            yyin = fopen(in, "r");
            if(yyin == NULL) {
                fprintf(stderr, "%s: file not found\n", in);
                return 2;
            }
            strcpy( out , out_file( in ));
            yyout = fopen( out , "w");
        }
    }


    // parsing du fichier
    yyparse();

    fclose(yyin);
    fclose(yyout);

    return 0;
}

